package net.tomp2p.holep.strategy;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.GenericFutureListener;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import net.tomp2p.connection.Dispatcher;
import net.tomp2p.futures.BaseFutureAdapter;
import net.tomp2p.futures.FutureChannelCreator;
import net.tomp2p.futures.FutureDone;
import net.tomp2p.futures.FutureResponse;
import net.tomp2p.holep.DuplicatesHandler;
import net.tomp2p.holep.NATHandlerImpl;
import net.tomp2p.holep.HolePScheduler;
import net.tomp2p.holep.NATType;
import net.tomp2p.message.Buffer;
import net.tomp2p.message.Message;
import net.tomp2p.message.Message.Type;
import net.tomp2p.p2p.Peer;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.peers.PeerSocketAddress;
import net.tomp2p.peers.PeerSocketAddress.PeerSocket4Address;
import net.tomp2p.peers.PeerSocketAddress.PeerSocket6Address;
import net.tomp2p.rpc.RPC;
import net.tomp2p.rpc.RPC.Commands;
import net.tomp2p.utils.Pair;
import net.tomp2p.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* DO NOT INSTANTIATE THIS CLASS! <br>
* <br>
*
* If you need to add a new supported nat type please extend this class and
* change also {@link NATType} and {@link NATTypeDetection}. <br>
* <br>
*
* This class is responsible for the whole hole punching procedure. It covers
* all aspects of the procedure on the sender and the recipient side.
*
*
* @author Jonas Wagner
*
*/
public abstract class AbstractHolePStrategy implements HolePStrategy {
private static final Logger LOG = LoggerFactory.getLogger(AbstractHolePStrategy.class);
private final int numberOfHoles;
private final int idleUDPSeconds;
private List<FutureResponse> futureResponses = new ArrayList<FutureResponse>();
private PeerAddress originalSender;
protected final Peer peer;
protected final Message originalMessage;
protected volatile List<ChannelFuture> channelFutures = new ArrayList<ChannelFuture>();
protected volatile List<Pair<Integer, Integer>> portMappings = new ArrayList<Pair<Integer, Integer>>();
/**
* This constructor should never be called by the user, since it should be
* called by its strategy pattern instances like
* {@link PortPreservingStrategy}.
*
* @param peer
* @param numberOfHoles
* @param idleUDPSeconds
* @param originalMessage
*/
protected AbstractHolePStrategy(final Peer peer, final int numberOfHoles, final int idleUDPSeconds, final Message originalMessage) {
this.peer = peer;
this.numberOfHoles = numberOfHoles;
this.idleUDPSeconds = idleUDPSeconds;
this.originalMessage = originalMessage;
LOG.trace("new HolePuncher created, originalMessage {}", originalMessage.toString());
}
/**
* This method cares about which socket contacts which socket on the NATs
* which are needed to be traversed.
*
* @param replyMessageFuture2
* @param replyMessage
*/
protected abstract void doPortGuessingTargetPeer(final Message replyMessage, final FutureDone<Message> replyMessageFuture2)
throws Exception;
/**
* This method needs to be overwritten by each strategy in order to let the
* other peer know which ports it need to contact.
*
* @param holePMessage
* @param initMessageFutureDone
* @param channelFutures2
*/
protected abstract void doPortGuessingInitiatingPeer(final Message holePMessage, final FutureDone<Message> initMessageFutureDone,
final List<ChannelFuture> channelFutures2) throws Exception;
/**
* This method does two things. If the initiating peer calls it, he gets
* back a {@link List} of new {@link SimpleInboundHandler} to deal with the
* replies of the replying peer. If a replying peer is calling this method
* it will return a {@link List} of default {@link SimpleInboundHandler}s
* from the {@link Dispatcher}.
*
* @param futureResponse
* @return handlerList
*/
protected List<Map<String, Pair<EventExecutorGroup, ChannelHandler>>> prepareHandlers(final boolean initiator,
final FutureDone<Message> futureDone) {
final List<Map<String, Pair<EventExecutorGroup, ChannelHandler>>> handlerList = new ArrayList<Map<String, Pair<EventExecutorGroup, ChannelHandler>>>(
numberOfHoles);
SimpleChannelInboundHandler<Message> inboundHandler;
Map<String, Pair<EventExecutorGroup, ChannelHandler>> handlers;
if (initiator) {
for (int i = 0; i < numberOfHoles; i++) {
// we need an own futureresponse for every hole we try to punch
futureResponses.add(new FutureResponse(originalMessage));
inboundHandler = createAfterHolePHandler(futureDone);
//TODO: enable
//handlers = peer.connectionBean().sender().configureHandlers(inboundHandler, futureResponses.get(i), idleUDPSeconds, false);
//handlerList.add(handlers);
}
} else {
inboundHandler = new DuplicatesHandler(peer.connectionBean().dispatcher());
for (int i = 0; i < numberOfHoles; i++) {
// we need an own futureresponse for every hole we try to punch
futureResponses.add(new FutureResponse(originalMessage));
//TODO: enable
//handlers = peer.connectionBean().sender().configureHandlers(inboundHandler, futureResponses.get(i), idleUDPSeconds, false);
//handlerList.add(handlers);
}
}
return handlerList;
}
/**
* This is a generic method which creates a number of {@link ChannelFuture}s
* and calls the associated {@link FutureDone} as soon as they're done.
*
* @param futureResponse
* @param handlersList
* @return fDoneChannelFutures
*/
protected FutureDone<List<ChannelFuture>> createChannelFutures(
final List<Map<String, Pair<EventExecutorGroup, ChannelHandler>>> handlersList, final FutureDone<Message> mainFutureDone,
final int numberOfHoles) {
final FutureDone<List<ChannelFuture>> fDoneChannelFutures = new FutureDone<List<ChannelFuture>>();
final AtomicInteger countDown = new AtomicInteger(numberOfHoles);
final List<ChannelFuture> channelFutures = new ArrayList<ChannelFuture>();
for (int i = 0; i < numberOfHoles; i++) {
final FutureResponse futureResponse = futureResponses.get(i);
final Map<String, Pair<EventExecutorGroup, ChannelHandler>> handlers = handlersList.get(i);
final FutureChannelCreator fcc = peer.connectionBean().reservation().create(1, 0);
Utils.addReleaseListener(fcc, futureResponse);
fcc.addListener(new BaseFutureAdapter<FutureChannelCreator>() {
@Override
public void operationComplete(final FutureChannelCreator future) throws Exception {
if (future.isSuccess()) {
//TODO: enable
/*final ChannelFuture cF = future.channelCreator().createUDP(BROADCAST_VALUE, handlers, futureResponse, false);
cF.addListener(new GenericFutureListener<ChannelFuture>() {
@Override
public void operationComplete(final ChannelFuture future) throws Exception {
if (future.isSuccess()) {
channelFutures.add(future);
} else {
mainFutureDone.failed("Error while creating the ChannelFutures!");
}
countDown.decrementAndGet();
if (countDown.get() == 0) {
fDoneChannelFutures.done(channelFutures);
}
}
});*/
} else {
countDown.decrementAndGet();
mainFutureDone.failed("Error while creating the ChannelFutures!");
}
}
});
}
return fDoneChannelFutures;
}
/**
* This method initiates the hole punch procedure.
*
* @param mainFutureDone
* @param originalChannelCreator
* @param originalFutureResponse
* @param natType
* @return mainFutureDone A FutureDone<Message> which if successful contains
* the response Message from the peer we want to contact
*/
public FutureDone<Message> initiateHolePunch(final FutureDone<Message> mainFutureDone, final FutureResponse originalFutureResponse) {
//check if testCase == true
/*if (((NATHandlerImpl) peer.peerBean().holePunchInitiator()).isTestCase()) {
mainFutureDone.failed("Gandalf says: You shall not pass!!!");
return mainFutureDone;
}*/
final FutureDone<List<ChannelFuture>> fDoneChannelFutures = createChannelFutures(prepareHandlers(true, mainFutureDone),
mainFutureDone, numberOfHoles);
fDoneChannelFutures.addListener(new BaseFutureAdapter<FutureDone<List<ChannelFuture>>>() {
@Override
public void operationComplete(final FutureDone<List<ChannelFuture>> future) throws Exception {
if (future.isSuccess()) {
final List<ChannelFuture> futures = future.object();
final FutureDone<Message> initMessage = createInitMessage(futures);
initMessage.addListener(new BaseFutureAdapter<FutureDone<Message>>() {
@Override
public void operationComplete(final FutureDone<Message> future) throws Exception {
if (future.isSuccess()) {
final Message initMessage = future.object();
sendHolePInitMessage(mainFutureDone, originalFutureResponse, futures, initMessage);
} else {
mainFutureDone.failed("The creation of the initMessage failed!");
}
}
});
} else {
mainFutureDone.failed("No ChannelFuture could be created!");
}
}
});
return mainFutureDone;
}
/**
* This method initiates the hole punch procedure on the target peer side.
*
* @return
*/
public FutureDone<Message> replyHolePunch() {
originalSender = (PeerAddress) originalMessage.neighborsSetList().get(0).neighbors().toArray()[0];
final FutureDone<Message> replyMessageFuture = new FutureDone<Message>();
final HolePStrategy thisInstance = this;
final FutureDone<List<ChannelFuture>> rmfChannelFutures = createChannelFutures(prepareHandlers(false, replyMessageFuture),
replyMessageFuture, numberOfHoles);
rmfChannelFutures.addListener(new BaseFutureAdapter<FutureDone<List<ChannelFuture>>>() {
@Override
public void operationComplete(final FutureDone<List<ChannelFuture>> future) throws Exception {
if (future.isSuccess()) {
channelFutures = future.object();
final FutureDone<Message> replyMessageFuture2 = createReplyMessage();
replyMessageFuture2.addListener(new BaseFutureAdapter<FutureDone<Message>>() {
@Override
public void operationComplete(final FutureDone<Message> future) throws Exception {
if (future.isSuccess()) {
final Message replyMessage = future.object();
//final Thread holePunchScheduler = new Thread(new HolePScheduler(peer.peerBean().holePNumberOfPunches(), thisInstance));
//holePunchScheduler.start();
replyMessageFuture.done(replyMessage);
} else {
replyMessageFuture2.failed("No ReplyMessage could be created!");
}
}
});
} else {
replyMessageFuture.failed("No ChannelFuture could be created!");
}
}
});
return replyMessageFuture;
}
/**
* This methods is only called by a {@link HolePScheduler}. It simply
* creates a dummyMessage and sends it from a given localPort (
* {@link ChannelFuture}) to a given remotePort. This procedure then punches
* the holes needed by the initiating {@link Peer}.
*
* @throws Exception
*/
public void tryConnect() throws Exception {
if (channelFutures.size() != portMappings.size()) {
throw new Exception("the number of channels does not match the number of ports!");
}
for (int i = 0; i < channelFutures.size(); i++) {
final Message dummyMessage = createDummyMessage(i);
final FutureResponse futureResponse = new FutureResponse(dummyMessage);
LOG.debug("FIRE! remote: {}, local: {}", dummyMessage.recipient(), dummyMessage.sender());
//peer.connectionBean().sender().afterConnect(futureResponse, dummyMessage, channelFutures.get(i), FIRE_AND_FORGET_VALUE);
}
}
/**
* This method is responsible for the send mechanism of the
* holePInitMessage.
*
* @param mainFutureDone
* @param originalFutureResponse
* @param futures
* @param initMessage
*/
private void sendHolePInitMessage(final FutureDone<Message> mainFutureDone, final FutureResponse originalFutureResponse,
final List<ChannelFuture> futures, final Message initMessage) {
final FutureChannelCreator fChannelCreator = peer.connectionBean().reservation().create(1, 0);
fChannelCreator.addListener(new BaseFutureAdapter<FutureChannelCreator>() {
@Override
public void operationComplete(final FutureChannelCreator future) throws Exception {
if (future.isSuccess()) {
final FutureResponse holePFutureResponse = new FutureResponse(originalMessage);
// we need to know if the setUp failed.
holePFutureResponse.addListener(new BaseFutureAdapter<FutureResponse>() {
@Override
public void operationComplete(final FutureResponse future) throws Exception {
if (!future.isSuccess()) {
mainFutureDone.failed("No port information could be exchanged");
}
}
});
Utils.addReleaseListener(future, holePFutureResponse);
// send the holePInitMessage to one of the target peer
// relays
//peer.connectionBean()
// .sender()
// .sendUDP(createHolePHandler(futures, mainFutureDone, originalFutureResponse), holePFutureResponse, initMessage,
// future.channelCreator(), idleUDPSeconds, BROADCAST_VALUE);
LOG.debug("ChannelFutures successfully created. Initialization of hole punching started.");
} else {
mainFutureDone.failed("The creation of the channelCreator for to send the initMessage failed!");
}
}
});
}
/**
* This method creates a {@link SimpleChannelInboundHandler} which sends the
* original{@link Message} to the nat peer that needs to be contacted.
*
* @param futures
* @param originalFutureResponse
* @param originalFutureResponse
* @return holePhandler
*/
private SimpleChannelInboundHandler<Message> createHolePHandler(final List<ChannelFuture> futures,
final FutureDone<Message> futureDone, final FutureResponse originalFutureResponse) {
final SimpleChannelInboundHandler<Message> holePunchInboundHandler = new SimpleChannelInboundHandler<Message>() {
@Override
protected void channelRead0(final ChannelHandlerContext ctx, final Message msg) throws Exception {
final List<Integer> portList = checkReplyValues(msg, futureDone);
if (portList != null) {
final int numberOfConnectionAttempts = portList.size() / 2;
final AtomicInteger countDown = new AtomicInteger(numberOfConnectionAttempts);
for (int i = 0; i < portList.size(); i++) {
// this ensures, that if all hole punch attempts fail,
// the system is still able to send the message via
// relaying without the user noticing it
final FutureResponse holePFutureResponse = handleFutureResponse(originalFutureResponse, portList, i, countDown,
numberOfConnectionAttempts);
final int localport = extractLocalPort(futureDone, portList, i);
final ChannelFuture channelFuture = extractChannelFuture(futures, localport);
if (channelFuture == null) {
futureDone.failed("Something went wrong with the portmappings!");
}
i++;
final Message sendMessage = createSendOriginalMessage(portList.get(i - 1), portList.get(i));
//peer.connectionBean().sender().afterConnect(holePFutureResponse, sendMessage, channelFuture, false);
LOG.debug("originalMessage has been sent to the other peer! {}", sendMessage);
}
}
}
/**
* this ensures, that if all hole punch attempts fail, the system is
* still able to send the message via relaying without the user
* noticing it. In case of a successful transmission, it also
* forwards the response message to the original FutureResponse.
*
* @param originalFutureResponse
* @param portList
* @param index
* @param countDown
* @param numberOfConnectionAttempts
* @return
*/
private FutureResponse handleFutureResponse(final FutureResponse originalFutureResponse, final List<Integer> portList,
final int index, final AtomicInteger countDown, final int numberOfConnectionAttempts) {
final int listIndex = index / 2;
final FutureResponse holePFutureResponse = futureResponses.get(listIndex);
holePFutureResponse.addListener(new BaseFutureAdapter<FutureResponse>() {
@Override
public void operationComplete(final FutureResponse future) throws Exception {
if (future.isSuccess()) {
if (!originalFutureResponse.isCompleted()) {
originalFutureResponse.response(future.responseMessage());
}
} else {
countDown.decrementAndGet();
if (countDown.get() == 0) {
originalFutureResponse.failed("All " + numberOfConnectionAttempts + " connection attempts failed!");
}
}
}
});
return holePFutureResponse;
}
/**
* ExtractLocalPort is a method which returns the portnumber of the
* previously assigned socket given a guessedPort. This method is
* needed, because the the ports on which the target peer will be
* contacted may not be the same as the ports which were assigned at
* the time of the creation of the channelFutures.
*
* @param futureDone
* @param portList
* @param index
* @return
*/
private int extractLocalPort(final FutureDone<Message> futureDone, final List<Integer> portList, final int index) {
int localport = -1;
if (portMappings.isEmpty()) {
localport = portList.get(index);
} else {
for (Pair<Integer, Integer> entry : portMappings) {
if ((int) entry.element0() == portList.get(index)) {
localport = (int) entry.element1();
}
}
}
if (localport < 1) {
futureDone.failed("No mapping available for port " + portList.get(index) + "!");
}
return localport;
}
};
LOG.debug("new HolePunchHandler created, waiting now for answer from rendez-vous peer.");
return holePunchInboundHandler;
}
/**
* This method creates the inboundHandler for the replyMessage of the peer
* that we want to send a message to.
*
* @return inboundHandler
*/
private SimpleChannelInboundHandler<Message> createAfterHolePHandler(final FutureDone<Message> mainFutureDone) {
final SimpleChannelInboundHandler<Message> inboundHandler = new SimpleChannelInboundHandler<Message>() {
@Override
protected synchronized void channelRead0(final ChannelHandlerContext ctx, final Message msg) throws Exception {
if (Message.Type.OK == msg.type() && originalMessage.command() == msg.command()) {
LOG.debug("Successfully transmitted the original message to peer:[" + msg.sender().toString()
+ "]. Now here's the reply:[" + msg.toString() + "]");
mainFutureDone.done(msg);
ctx.close();
} else if (Message.Type.REQUEST_3 == msg.type() && Commands.HOLEP.getNr() == msg.command()) {
LOG.debug("Holes successfully punched with ports = {local = " + msg.recipient() + " , remote = "
+ msg.sender() + "}!");
} else {
LOG.debug("Holes punche not punched with ports = {local = " + msg.recipient() + " , remote = "
+ msg.sender() + "} yet!");
}
}
};
return inboundHandler;
}
/**
* This method looks up a {@Link ChannelFuture} from the
* channelFutures {@link List}. If the {@Link ChannelFuture} can't be
* found it returns null instead.
*
* @param futures
* @param localPort
* @return
*/
private ChannelFuture extractChannelFuture(final List<ChannelFuture> futures, final int localPort) {
for (ChannelFuture future : futures) {
if (future.channel().localAddress() != null) {
final InetSocketAddress inetSocketAddress = (InetSocketAddress) future.channel().localAddress();
if (inetSocketAddress.getPort() == localPort) {
return future;
}
}
}
return null;
}
/**
* this method checks if the returned values from the replying nat peer are
* valid.
*
* @param msg
* @return ok
*/
@SuppressWarnings("unchecked")
private List<Integer> checkReplyValues(final Message msg, final FutureDone<Message> futureDone) {
if (msg.command() == Commands.HOLEP.getNr() && msg.type() == Type.OK) {
List<Integer> portList = null;
try {
portList = (List<Integer>) Utils.decodeJavaObject(msg.buffer(0).buffer());
} catch (final Exception e) {
futureDone.failed("The decoding of the buffer threw an exception!");
e.printStackTrace();
return null;
}
// the list with the ports should never be Empty
if (!portList.isEmpty()) {
final int rawNumberOfHoles = portList.size();
// the number of ports must be even!
if ((rawNumberOfHoles % 2) == 0) {
return portList;
} else {
futureDone.failed("The number of ports in the Buffer was odd! This should never happen");
}
} else {
futureDone.failed("IntList in replyMessage was null or Empty! No ports available!!!!");
}
} else {
futureDone.failed("Could not acquire a connection via hole punching, got: " + msg);
}
return null;
}
/**
* This method avoids duplicate code.
*
* @param portList
* @return
* @throws IOException
*/
protected Buffer encodePortList(final List<Integer> portList) throws IOException {
final byte[] bytes = Utils.encodeJavaObject(portList);
final Buffer byteBuf = new Buffer(Unpooled.wrappedBuffer(bytes));
return byteBuf;
}
/*
* =============================== MESSAGES ===============================
*/
/**
* This method duplicates the original {@link Message} multiple times. This
* is needed, because the {@link Buffer} can only be read once.
*
* @param originalMessage
* @param localPort
* @param remotePort
* @return
*/
private Message createSendOriginalMessage(final int localPort, final int remotePort) {
final PeerSocket4Address ps4a1 = originalMessage.sender().ipv4Socket().withUdpPort(localPort);
final PeerAddress sender = originalMessage.sender().withUnreachable(false).withIPSocket(ps4a1).withRelaySize(0);
final PeerSocket4Address ps4a2 = originalMessage.recipient().ipv4Socket().withUdpPort(remotePort);
final PeerAddress recipient = originalMessage.recipient().withUnreachable(false).withIPSocket(ps4a2).withRelaySize(0);
final Message sendMessage = createHolePMessage(recipient, sender, originalMessage.command(), originalMessage.type());
sendMessage.version(originalMessage.version());
sendMessage.intValue(originalMessage.messageId());
sendMessage.udp(true);
sendMessage.expectDuplicate(true);
for (Buffer buf : originalMessage.bufferList()) {
sendMessage.buffer(new Buffer(buf.buffer().duplicate()));
}
return sendMessage;
}
/**
* This method creates the initial {@link Message} with {@link Commands}
* .HOLEP and {@link Type}.REQUEST_1. This {@link Message} will be forwarded
* to the rendez-vous server (a relay of the remote peer) and initiate the
* hole punching procedure on the other peer. This method also calls the
* doPortGuessingInitiatingPeer(...) method of its subclass implementation
* in order to gain the correct ports.
*
* @param message
* @param channelCreator
* @return holePMessage
*/
private FutureDone<Message> createInitMessage(final List<ChannelFuture> channelFutures) throws Exception {
final FutureDone<Message> initMessageFutureDone = new FutureDone<Message>();
final PeerSocketAddress socketAddress = Utils.extractRandomRelay(originalMessage);
// we need to make a copy of the original Message
final PeerAddress recipient;
if(socketAddress instanceof PeerSocket4Address) {
recipient = originalMessage.recipient().withIpv4Socket((PeerSocket4Address)socketAddress).withRelaySize(0).withIpv6Socket(null);
} else {
recipient = originalMessage.recipient().withIpv6Socket((PeerSocket6Address)socketAddress).withRelaySize(0).withIpv4Socket(null);
}
final Message initMessage = createHolePMessage(recipient, originalMessage.sender(), RPC.Commands.HOLEP.getNr(), Message.Type.REQUEST_1);
initMessage.version(originalMessage.version());
initMessage.udp(true);
doPortGuessingInitiatingPeer(initMessage, initMessageFutureDone, channelFutures);
LOG.debug("Hole punch initMessage created {}", initMessage.toString());
return initMessageFutureDone;
}
/**
* This method will create so called dummy messages without any content.
* Such methods are needed to create the port mapping entries in the peers
* NAT device.
*
* @param index
* i
* @return dummyMessage
*/
private Message createDummyMessage(final int index) {
final int remotePort = portMappings.get(index).element0();
final int localPort = portMappings.get(index).element1();
final PeerSocket4Address ps4a1 = peer.peerBean().serverPeerAddress().ipv4Socket().withUdpPort(localPort);
final PeerAddress sender = peer.peerBean().serverPeerAddress().withIPSocket(ps4a1);
final PeerSocket4Address ps4a2 = originalMessage.recipient().ipv4Socket().withUdpPort(remotePort);
final PeerAddress recipient = originalMessage.recipient().withUnreachable(false).withIPSocket(ps4a2);
final Message dummyMessage = createHolePMessage(recipient, sender, RPC.Commands.HOLEP.getNr(), Message.Type.REQUEST_3);
dummyMessage.udp(true);
return dummyMessage;
}
/**
* This method creates the reply {@link Message} with {@link Commands}
* .HOLEP and {@link Type}.REQUEST_2. This method also calls the
* doPortGuessingTargetPeer(...) method of its subclass implementation in
* order to gain the correct ports.
*
* @return
* @throws Exception
*/
private FutureDone<Message> createReplyMessage() throws Exception {
final FutureDone<Message> replyMessageFuture2 = new FutureDone<Message>();
final Message replyMessage = createHolePMessage(originalMessage.sender(), peer.peerBean().serverPeerAddress(), Commands.HOLEP.getNr(),
Type.OK);
replyMessage.messageId(originalMessage.messageId());
doPortGuessingTargetPeer(replyMessage, replyMessageFuture2);
return replyMessageFuture2;
}
/**
* This is a generic method which creates a {@link Message} with the basic
* parameters. The method avoids duplicate code.
*
* @param recipient
* @param sender
* @param command
* @param type
* @return holePMessage
*/
private Message createHolePMessage(final PeerAddress recipient, final PeerAddress sender, final byte command, final Message.Type type) {
final Message message = new Message();
message.recipient(recipient);
message.sender(sender);
message.command(command);
message.type(type);
return message;
}
}